/**
 * Copyright 2009 Joe LaPenna
 */

package com.joelapenna.foursquare;

import com.joelapenna.foursquare.error.FoursquareCredentialsException;
import com.joelapenna.foursquare.error.FoursquareError;
import com.joelapenna.foursquare.error.FoursquareException;
import com.joelapenna.foursquare.error.FoursquareParseException;
import com.joelapenna.foursquare.http.HttpImpl;
import com.joelapenna.foursquare.parsers.json.CategoryParser;
import com.joelapenna.foursquare.parsers.json.CheckinParser;
import com.joelapenna.foursquare.parsers.json.CheckinResultParser;
import com.joelapenna.foursquare.parsers.json.CityParser;
import com.joelapenna.foursquare.parsers.json.FriendInvitesResultParser;
import com.joelapenna.foursquare.parsers.json.GroupParser;
import com.joelapenna.foursquare.parsers.json.ResponseParser;
import com.joelapenna.foursquare.parsers.json.SettingsParser;
import com.joelapenna.foursquare.parsers.json.TipParser;
import com.joelapenna.foursquare.parsers.json.TodoParser;
import com.joelapenna.foursquare.parsers.json.UserParser;
import com.joelapenna.foursquare.parsers.json.VenueParser;
import com.joelapenna.foursquare.types.Category;
import com.joelapenna.foursquare.types.Checkin;
import com.joelapenna.foursquare.types.CheckinResult;
import com.joelapenna.foursquare.types.City;
import com.joelapenna.foursquare.types.FriendInvitesResult;
import com.joelapenna.foursquare.types.Group;
import com.joelapenna.foursquare.types.Response;
import com.joelapenna.foursquare.types.Settings;
import com.joelapenna.foursquare.types.Tip;
import com.joelapenna.foursquare.types.Todo;
import com.joelapenna.foursquare.types.User;
import com.joelapenna.foursquare.types.Venue;
import com.joelapenna.foursquare.util.JSONUtils;
import com.joelapenna.foursquared.util.Base64Coder;

import org.apache.http.message.BasicNameValuePair;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.logging.Logger;

/**
 * @author Joe LaPenna (joe@joelapenna.com)
 */
class FoursquareHttpApiV1 {
    private static final Logger LOG = Logger
            .getLogger(FoursquareHttpApiV1.class.getCanonicalName());
    private static final boolean DEBUG = Foursquare.DEBUG;

    private static final String DATATYPE = ".json";
    
    private static final String URL_API_ADDVENUE = "/addvenue";
    private static final String URL_API_ADDTIP = "/addtip";
    private static final String URL_API_CITIES = "/cities";
    private static final String URL_API_CHECKINS = "/checkins";
    private static final String URL_API_CHECKIN = "/checkin";
    private static final String URL_API_USER = "/user";
    private static final String URL_API_VENUE = "/venue";
    private static final String URL_API_VENUES = "/venues";
    private static final String URL_API_TIPS = "/tips";
    private static final String URL_API_TODOS = "/todos";
    private static final String URL_API_FRIEND_REQUESTS = "/friend/requests";
    private static final String URL_API_FRIEND_APPROVE = "/friend/approve";
    private static final String URL_API_FRIEND_DENY = "/friend/deny";
    private static final String URL_API_FRIEND_SENDREQUEST = "/friend/sendrequest";
    private static final String URL_API_FRIENDS = "/friends";
    private static final String URL_API_FIND_FRIENDS_BY_NAME = "/findfriends/byname";
    private static final String URL_API_FIND_FRIENDS_BY_PHONE = "/findfriends/byphone";
    private static final String URL_API_FIND_FRIENDS_BY_FACEBOOK = "/findfriends/byfacebook";
    private static final String URL_API_FIND_FRIENDS_BY_TWITTER = "/findfriends/bytwitter";
    private static final String URL_API_CATEGORIES = "/categories";
    private static final String URL_API_HISTORY = "/history";
    private static final String URL_API_FIND_FRIENDS_BY_PHONE_OR_EMAIL = "/findfriends/byphoneoremail";
    private static final String URL_API_INVITE_BY_EMAIL = "/invite/byemail";
    private static final String URL_API_SETPINGS = "/settings/setpings";
    private static final String URL_API_VENUE_FLAG_CLOSED = "/venue/flagclosed";
    private static final String URL_API_VENUE_FLAG_MISLOCATED = "/venue/flagmislocated";
    private static final String URL_API_VENUE_FLAG_DUPLICATE = "/venue/flagduplicate";
    private static final String URL_API_VENUE_PROPOSE_EDIT = "/venue/proposeedit";
    private static final String URL_API_USER_UPDATE = "/user/update";
    private static final String URL_API_MARK_TODO = "/mark/todo";
    private static final String URL_API_MARK_IGNORE = "/mark/ignore";
    private static final String URL_API_MARK_DONE = "/mark/done";
    private static final String URL_API_UNMARK_TODO = "/unmark/todo";
    private static final String URL_API_UNMARK_DONE = "/unmark/done";
    private static final String URL_API_TIP_DETAIL = "/tip/detail";
    
    //private final DefaultHttpClient mHttpClient = AbstractHttpApi.createHttpClient();
    //private HttpApi mHttpApi;

    private final String mApiBaseUrl;
    //private final AuthScope mAuthScope;

    private HttpImpl mHttp;
    
    
    public FoursquareHttpApiV1(String domain, String clientVersion) {
        mApiBaseUrl = "https://" + domain + "/v1";
       // mAuthScope = new AuthScope(domain, 80);
        
       // mHttpApi = new HttpApiWithBasicAuth(mHttpClient, clientVersion);
        mHttp = new HttpImpl(clientVersion);
    }
    
    public boolean hasCredentials() {
        return mHttp.getHasCredentials();
    }

    void setCredentials(String phone, String password) {
        if (phone == null || phone.length() == 0 || password == null || password.length() == 0) {
            mHttp.setCredentials(null, null);
        } else {
            mHttp.setCredentials(phone, password);
        }
    }

    /*
     * /addtip?vid=1234&text=I%20added%20a%20tip&type=todo (type defaults "tip")
     */
    Tip addtip(String vid, String text, String type, String geolat, String geolong, String geohacc,
            String geovacc, String geoalt) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_ADDTIP), 
                new TipParser(),
                new BasicNameValuePair("vid", vid), //
                new BasicNameValuePair("text", text), //
                new BasicNameValuePair("type", type), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }

    /**
     * @param name the name of the venue
     * @param address the address of the venue (e.g., "202 1st Avenue")
     * @param crossstreet the cross streets (e.g., "btw Grand & Broome")
     * @param city the city name where this venue is
     * @param state the state where the city is
     * @param zip (optional) the ZIP code for the venue
     * @param phone (optional) the phone number for the venue
     * @return
     * @throws FoursquareException
     * @throws FoursquareCredentialsException
     * @throws FoursquareError
     * @throws IOException
     */
    Venue addvenue(String name, String address, String crossstreet, String city, String state,
            String zip, String phone, String categoryId, String geolat, String geolong, String geohacc,
            String geovacc, String geoalt) throws Exception {
        return (Venue)mHttp.httpPost(fullUrl(URL_API_ADDVENUE), //
                new VenueParser(),
                new BasicNameValuePair("name", name), //
                new BasicNameValuePair("address", address), //
                new BasicNameValuePair("crossstreet", crossstreet), //
                new BasicNameValuePair("city", city), //
                new BasicNameValuePair("state", state), //
                new BasicNameValuePair("zip", zip), //
                new BasicNameValuePair("phone", phone), //
                new BasicNameValuePair("primarycategoryid", categoryId), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }

    /*
     * /cities
     */
    @SuppressWarnings("unchecked")
    Group<City> cities() throws Exception {
        return (Group<City>)mHttp.httpGet(
                fullUrl(URL_API_CITIES),
                new GroupParser(new CityParser()));
    }

    /*
     * /checkins?
     */
    @SuppressWarnings("unchecked")
    Group<Checkin> checkins(String geolat, String geolong, String geohacc, String geovacc,
            String geoalt) throws Exception {
        return (Group<Checkin>)mHttp.httpGet(
                fullUrl(URL_API_CHECKINS), //
                new GroupParser(new CheckinParser()),
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }

    /*
     * /checkin?vid=1234&venue=Noc%20Noc&shout=Come%20here&private=0&twitter=1
     */
    CheckinResult checkin(String vid, String venue, String geolat, String geolong, String geohacc,
            String geovacc, String geoalt, String shout, boolean isPrivate, boolean tellFollowers,
            boolean twitter, boolean facebook) throws Exception {
        return (CheckinResult)mHttp.httpPost(
                fullUrl(URL_API_CHECKIN), //
                new CheckinResultParser(),
                new BasicNameValuePair("vid", vid), //
                new BasicNameValuePair("venue", venue), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt), //
                new BasicNameValuePair("shout", shout), //
                new BasicNameValuePair("private", (isPrivate) ? "1" : "0"), //
                new BasicNameValuePair("followers", (tellFollowers) ? "1" : "0"), //
                new BasicNameValuePair("twitter", (twitter) ? "1" : "0"), //
                new BasicNameValuePair("facebook", (facebook) ? "1" : "0"), //
                new BasicNameValuePair("markup", "android")); // used only by android for checkin result 'extras'.
    }

    /**
     * /user?uid=9937
     */
    User user(String uid, boolean mayor, boolean badges, boolean stats, String geolat, String geolong,
            String geohacc, String geovacc, String geoalt) throws Exception {
        return (User)mHttp.httpGet(
                fullUrl(URL_API_USER), //
                new UserParser(),
                new BasicNameValuePair("uid", uid), //
                new BasicNameValuePair("mayor", (mayor) ? "1" : "0"), //
                new BasicNameValuePair("badges", (badges) ? "1" : "0"), //
                new BasicNameValuePair("stats", (stats) ? "1" : "0"), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }

    /**
     * /venues?geolat=37.770900&geolong=-122.43698
     */
    @SuppressWarnings("unchecked")
    Group<Group<Venue>> venues(String geolat, String geolong, String geohacc, String geovacc,
            String geoalt, String query, int limit) throws Exception {
        return (Group<Group<Venue>>)mHttp.httpGet(
                fullUrl(URL_API_VENUES), //
                new GroupParser(new GroupParser(new VenueParser())),
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt), //
                new BasicNameValuePair("q", query), //
                new BasicNameValuePair("l", String.valueOf(limit)));
    }

    /**
     * /venue?vid=1234
     */
    Venue venue(String vid, String geolat, String geolong, String geohacc, String geovacc,
            String geoalt) throws Exception {
        return (Venue)mHttp.httpGet(
                fullUrl(URL_API_VENUE), //
                new VenueParser(),
                new BasicNameValuePair("vid", vid), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }
    
    /**
     * /tips?geolat=37.770900&geolong=-122.436987&l=1
     */
    @SuppressWarnings("unchecked")
    Group<Tip> tips(String geolat, String geolong, String geohacc, String geovacc,
            String geoalt, String uid, String filter, String sort, int limit) throws Exception {
        return (Group<Tip>)mHttp.httpGet(
                fullUrl(URL_API_TIPS), //
                new GroupParser(new TipParser()),
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt), //
                new BasicNameValuePair("uid", uid), //
                new BasicNameValuePair("filter", filter), //
                new BasicNameValuePair("sort", sort), //
                new BasicNameValuePair("l", String.valueOf(limit)));
    }
    
    /**
     * /todos?geolat=37.770900&geolong=-122.436987&l=1&sort=[recent|nearby]
     */
    @SuppressWarnings("unchecked")
    Group<Todo> todos(String uid, String geolat, String geolong, String geohacc, String geovacc,
            String geoalt, boolean recent, boolean nearby, int limit) throws Exception {
        String sort = null;
        if (recent) {
            sort = "recent";
        } else if (nearby) {
            sort = "nearby";
        }
        return (Group<Todo>)mHttp.httpGet(
                fullUrl(URL_API_TODOS), //
                new GroupParser(new TodoParser()),
                new BasicNameValuePair("uid", uid), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt), //
                new BasicNameValuePair("sort", sort), //
                new BasicNameValuePair("l", String.valueOf(limit)));
    }
    
    /*
     * /friends?uid=9937
     */
    @SuppressWarnings("unchecked")
    Group<User> friends(String uid, String geolat, String geolong, String geohacc, String geovacc,
            String geoalt) throws Exception {
        return (Group<User>)mHttp.httpGet(
                fullUrl(URL_API_FRIENDS), //
                new GroupParser(new UserParser()),
                new BasicNameValuePair("uid", uid), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }

    /*
     * /friend/requests
     */
    @SuppressWarnings("unchecked")
    Group<User> friendRequests() throws Exception {
        return (Group<User>)mHttp.httpGet(
             fullUrl(URL_API_FRIEND_REQUESTS),
             new GroupParser(new UserParser()));
    }

    /*
     * /friend/approve?uid=9937
     */
    User friendApprove(String uid) throws Exception {
        return (User)mHttp.httpPost(
            fullUrl(URL_API_FRIEND_APPROVE),
            new UserParser(),
            new BasicNameValuePair("uid", uid));
    }

    /*
     * /friend/deny?uid=9937
     */
    User friendDeny(String uid) throws Exception {
        return (User)mHttp.httpPost(
            fullUrl(URL_API_FRIEND_DENY),
            new UserParser(),
            new BasicNameValuePair("uid", uid));
    }

    /*
     * /friend/sendrequest?uid=9937
     */
    User friendSendrequest(String uid) throws Exception {
        return (User)mHttp.httpPost(
            fullUrl(URL_API_FRIEND_SENDREQUEST), 
            new UserParser(),
            new BasicNameValuePair("uid", uid));
    }

    /**
     * /findfriends/byname?q=john doe, mary smith
     */
    @SuppressWarnings("unchecked")
    public Group<User> findFriendsByName(String text) throws Exception {
        return (Group<User>)mHttp.httpGet(
                fullUrl(URL_API_FIND_FRIENDS_BY_NAME), 
                new GroupParser(new UserParser()),
                new BasicNameValuePair("q", text));
    }

    /**
     * /findfriends/byphone?q=555-5555,555-5556
     */
    @SuppressWarnings("unchecked")
    public Group<User> findFriendsByPhone(String text) throws Exception {
        return (Group<User>)mHttp.httpGet(
                fullUrl(URL_API_FIND_FRIENDS_BY_PHONE), 
                new GroupParser(new UserParser()),
                new BasicNameValuePair("q", text));
    }

    /**
     * /findfriends/byfacebook?q=friendid,friendid,friendid
     */
    @SuppressWarnings("unchecked")
    public Group<User> findFriendsByFacebook(String text) throws Exception {
        return (Group<User>)mHttp.httpPost(
                fullUrl(URL_API_FIND_FRIENDS_BY_FACEBOOK), 
                new GroupParser(new UserParser()),
                new BasicNameValuePair("q", text));
    }
    
    /**
     * /findfriends/bytwitter?q=yourtwittername
     */
    @SuppressWarnings("unchecked")
    public Group<User> findFriendsByTwitter(String text) throws Exception {
        return (Group<User>)mHttp.httpGet(
                fullUrl(URL_API_FIND_FRIENDS_BY_TWITTER), 
                new GroupParser(new UserParser()),
                new BasicNameValuePair("q", text));
    }
    
    /**
     * /categories
     */
    @SuppressWarnings("unchecked")
    public Group<Category> categories() throws Exception {
        return (Group<Category>)mHttp.httpGet(
                fullUrl(URL_API_CATEGORIES),
                new GroupParser(new CategoryParser()));
    }

    /**
     * /history
     */
    @SuppressWarnings("unchecked")
    public Group<Checkin> history(String limit, String sinceid) throws Exception {
        return (Group<Checkin>)mHttp.httpGet(
            fullUrl(URL_API_HISTORY),
            new GroupParser(new CheckinParser()),
            new BasicNameValuePair("l", limit),
            new BasicNameValuePair("sinceid", sinceid));
    }
    
    /**
     * /mark/todo
     */
    public Todo markTodo(String tid) throws Exception {
        return (Todo)mHttp.httpPost(
                fullUrl(URL_API_MARK_TODO), 
                new TodoParser(),
                new BasicNameValuePair("tid", tid));
    }
    
    /**
     * This is a hacky special case, hopefully the api will be updated in v2 for this.
     * /mark/todo
     */
    public Todo markTodoVenue(String vid) throws Exception {
        return (Todo)mHttp.httpPost(
                fullUrl(URL_API_MARK_TODO),
                new TodoParser(),
                new BasicNameValuePair("vid", vid));
    }
    
    /**
     * /mark/ignore
     */
    public Tip markIgnore(String tid) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_MARK_IGNORE),
                new TipParser(),
                new BasicNameValuePair("tid", tid));
    }
    
    /**
     * /mark/done
     */
    public Tip markDone(String tid) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_MARK_DONE),
                new TipParser(),
                new BasicNameValuePair("tid", tid));
    }
    
    /**
     * /unmark/todo
     */
    public Tip unmarkTodo(String tid) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_UNMARK_TODO),
                new TipParser(),
                new BasicNameValuePair("tid", tid));
    }
    
    /**
     * /unmark/done
     */
    public Tip unmarkDone(String tid) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_UNMARK_DONE), 
                new TipParser(),
                new BasicNameValuePair("tid", tid));
    }

    /**
     * /tip/detail?tid=1234
     */
    public Tip tipDetail(String tid) throws Exception {
        return (Tip)mHttp.httpPost(
                fullUrl(URL_API_TIP_DETAIL), 
                new TipParser(),
                new BasicNameValuePair("tid", tid));
    }

    /**
     * /findfriends/byphoneoremail?p=comma-sep-list-of-phones&e=comma-sep-list-of-emails
     */
    public FriendInvitesResult findFriendsByPhoneOrEmail(String phones, String emails) throws Exception {
        return (FriendInvitesResult)mHttp.httpPost(
                fullUrl(URL_API_FIND_FRIENDS_BY_PHONE_OR_EMAIL),
                new FriendInvitesResultParser(),
                new BasicNameValuePair("p", phones),
                new BasicNameValuePair("e", emails));
    }
    
    /**
     * /invite/byemail?q=comma-sep-list-of-emails
     */
    public Response inviteByEmail(String emails) throws Exception {
        return (Response)mHttp.httpPost(
                fullUrl(URL_API_INVITE_BY_EMAIL), 
                new ResponseParser(),
                new BasicNameValuePair("q", emails));
    }
    
    /**
     * /settings/setpings?self=[on|off]
     */
    public Settings setpings(boolean on) throws Exception {
        return (Settings)mHttp.httpPost(
                fullUrl(URL_API_SETPINGS), 
                new SettingsParser(),
                new BasicNameValuePair("self", on ? "on" : "off"));
    }
    
    /**
     * /settings/setpings?uid=userid
     */
    public Settings setpings(String userid, boolean on) throws Exception {
        return (Settings)mHttp.httpPost(
                fullUrl(URL_API_SETPINGS), 
                new SettingsParser(),
                new BasicNameValuePair(userid, on ? "on" : "off"));
    }
    
    /**
     * /venue/flagclosed?vid=venueid
     */
    public Response flagclosed(String venueId) throws Exception {
        return (Response)mHttp.httpPost(
                fullUrl(URL_API_VENUE_FLAG_CLOSED), 
                new ResponseParser(),
                new BasicNameValuePair("vid", venueId));
    }

    /**
     * /venue/flagmislocated?vid=venueid
     */
    public Response flagmislocated(String venueId) throws Exception {
        return (Response)mHttp.httpPost(
                fullUrl(URL_API_VENUE_FLAG_MISLOCATED), 
                new ResponseParser(),
                new BasicNameValuePair("vid", venueId));
    }

    /**
     * /venue/flagduplicate?vid=venueid
     */
    public Response flagduplicate(String venueId) throws Exception {
        return (Response)mHttp.httpPost(
                fullUrl(URL_API_VENUE_FLAG_DUPLICATE), 
                new ResponseParser(),
                new BasicNameValuePair("vid", venueId));
    }
    
    /**
     * /venue/prposeedit?vid=venueid&name=...
     */
    public Response proposeedit(String venueId, String name, String address, String crossstreet, 
            String city, String state, String zip, String phone, String categoryId, String geolat, 
            String geolong, String geohacc, String geovacc, String geoalt) throws Exception {
        return (Response)mHttp.httpPost(
                fullUrl(URL_API_VENUE_PROPOSE_EDIT), //
                new ResponseParser(),
                new BasicNameValuePair("vid", venueId), //
                new BasicNameValuePair("name", name), //
                new BasicNameValuePair("address", address), //
                new BasicNameValuePair("crossstreet", crossstreet), //
                new BasicNameValuePair("city", city), //
                new BasicNameValuePair("state", state), //
                new BasicNameValuePair("zip", zip), //
                new BasicNameValuePair("phone", phone), //
                new BasicNameValuePair("primarycategoryid", categoryId), //
                new BasicNameValuePair("geolat", geolat), //
                new BasicNameValuePair("geolong", geolong), //
                new BasicNameValuePair("geohacc", geohacc), //
                new BasicNameValuePair("geovacc", geovacc), //
                new BasicNameValuePair("geoalt", geoalt));
    }
    
    private String fullUrl(String url) {
        return mApiBaseUrl + url + DATATYPE;
    }
    
    /**
     * /user/update
     * Need to bring this method under control like the rest of the api methods. Leaving it 
     * in this state as authorization will probably switch from basic auth in the near future
     * anyway, will have to be updated. Also unlike the other methods, we're sending up data
     * which aren't basic name/value pairs.
     */
    public User userUpdate(String imagePathToJpg, String username, String password) 
        throws SocketTimeoutException, IOException, FoursquareError, FoursquareParseException {
        String BOUNDARY = "------------------319831265358979362846";
        String lineEnd = "\r\n"; 
        String twoHyphens = "--";
        int maxBufferSize = 8192;
        
        File file = new File(imagePathToJpg);
        FileInputStream fileInputStream = new FileInputStream(file);
        
        URL url = new URL(fullUrl(URL_API_USER_UPDATE));
        HttpURLConnection conn = mHttp.createHttpURLConnectionPost(url, BOUNDARY);
        conn.setRequestProperty("Authorization", "Basic " +  Base64Coder.encodeString(username + ":" + password));
        
        // We are always saving the image to a jpg so we can use .jpg as the extension below.
        DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); 
        dos.writeBytes(twoHyphens + BOUNDARY + lineEnd); 
        dos.writeBytes("Content-Disposition: form-data; name=\"image,jpeg\";filename=\"" + "image.jpeg" +"\"" + lineEnd); 
        dos.writeBytes("Content-Type: " + "image/jpeg" + lineEnd);
        dos.writeBytes(lineEnd); 
        
        int bytesAvailable = fileInputStream.available(); 
        int bufferSize = Math.min(bytesAvailable, maxBufferSize); 
        byte[] buffer = new byte[bufferSize]; 
        
        int bytesRead = fileInputStream.read(buffer, 0, bufferSize); 
        int totalBytesRead = bytesRead;
        while (bytesRead > 0) {
            dos.write(buffer, 0, bufferSize); 
            bytesAvailable = fileInputStream.available(); 
            bufferSize = Math.min(bytesAvailable, maxBufferSize); 
            bytesRead = fileInputStream.read(buffer, 0, bufferSize); 
            totalBytesRead = totalBytesRead  + bytesRead;
        }
        dos.writeBytes(lineEnd); 
        dos.writeBytes(twoHyphens + BOUNDARY + twoHyphens + lineEnd); 
        
        fileInputStream.close(); 
        dos.flush(); 
        dos.close(); 
     
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuilder response = new StringBuilder();
        String responseLine = "";
        while ((responseLine = in.readLine()) != null) {
            response.append(responseLine);
        }
        in.close();

        try {
            return (User)JSONUtils.consume(new UserParser(), response.toString());
        } catch (Exception ex) {
            throw new FoursquareParseException(
                    "Error parsing user photo upload response, invalid json.");
        }
    }
}
